package com.atguigu.tingshu.account.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.RechargeInfoMapper;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Maps;

import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.model.account.UserAccount;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private UserAccountDetailMapper userAccountDetailMapper;


	@Autowired
	private RechargeInfoMapper rechargeInfoMapper;

	@Override
	public void initAccount(long userId) {
		UserAccount userAccount = new UserAccount();
		userAccount.setUserId(userId);
		userAccount.setTotalAmount(new BigDecimal("0"));
		userAccount.setLockAmount(new BigDecimal("0"));
		userAccount.setAvailableAmount(new BigDecimal("0"));
		userAccount.setTotalIncomeAmount(new BigDecimal("0"));
		userAccount.setTotalPayAmount(new BigDecimal("0"));

		userAccountMapper.insert(userAccount);

	}

	@Override
	public BigDecimal getAvailableAmount() {

		// 获取当前登录用户的id
		Long userId = AuthContextHolder.getUserId();
		LambdaQueryWrapper<UserAccount> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(UserAccount::getUserId , userId) ;
		lambdaQueryWrapper.select(UserAccount::getAvailableAmount) ;
		UserAccount userAccount = userAccountMapper.selectOne(lambdaQueryWrapper);

		return userAccount.getAvailableAmount();
	}

	@Override
	@Transactional
	public AccountLockResultVo checkAndLockAccount(AccountLockVo accountLockVo) {


		// 使用Redis保证接口的幂等性(setnx)
		String idempotentKey = "check:idempotent:" + accountLockVo.getOrderNo() ;
		String accountLockResultKey = "account:lock:result:" + accountLockVo.getOrderNo() ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);
		if(!ifAbsent) {  // 重复请求

			// 从Redis中获取锁定结果
			String accounLockResultVoJSON = (String) redisTemplate.opsForValue().get(accountLockResultKey);
			if(!StringUtils.isEmpty(accounLockResultVoJSON)) {
				return JSON.parseObject(accounLockResultVoJSON , AccountLockResultVo.class) ;
			}else {
				throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT) ;
			}

		}

		// 检查可用金额
		UserAccount userAccount = userAccountMapper.check(accountLockVo.getUserId() , accountLockVo.getAmount());
		if(userAccount == null) {		// 账户的可用金额不够
			redisTemplate.delete(idempotentKey) ;			// 删除Redis中保证接口幂等性的数据key
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS) ;
		}

		// 锁定账户的可用金额
		int count = userAccountMapper.lock(accountLockVo.getUserId() , accountLockVo.getAmount()) ;		// 返回值表示的是影响的数据行数
		if(count == 0) {		// 账户可用金额锁定失败了
			redisTemplate.delete(idempotentKey) ;			// 删除Redis中保证接口幂等性的数据key
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;
		}


		/**
		 * 记录账户变更的日志数据，就是向user_account_detail表中插入数据
		 */
		this.log(accountLockVo.getUserId() , "锁定:" + accountLockVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_LOCK , accountLockVo.getAmount() , accountLockVo.getOrderNo()) ;

		// 构建响应结果数据
		AccountLockResultVo accountLockResultVo = new AccountLockResultVo() ;
		accountLockResultVo.setUserId(accountLockVo.getUserId());
		accountLockResultVo.setAmount(accountLockVo.getAmount());
		accountLockResultVo.setContent(accountLockVo.getContent());

		// 把锁定结果存储到Redis中
		redisTemplate.opsForValue().set(accountLockResultKey , JSON.toJSONString(accountLockResultVo) , 1 , TimeUnit.HOURS);

		// 返回
		return accountLockResultVo;
	}

	@Override
	public void minusAccount(String orderNo) {

		// 考虑接口的幂等性处理   setnx
		String idempotentKey = "account:minus:idempotent:" + orderNo ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 1, TimeUnit.HOURS);
		if(!ifAbsent) return ;

		// 根据订单的编号，从Redis中查询锁定结果数据
		String accountLockResultKey = "account:lock:result:" + orderNo ;
		String accountLockResultVoJson = (String) redisTemplate.opsForValue().get(accountLockResultKey);
		AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultVoJson, AccountLockResultVo.class);

		// 更改user_account数据库表中的相关字段的值
		int count = userAccountMapper.minusAccount(accountLockResultVo.getUserId() , accountLockResultVo.getAmount()) ;
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR) ;
		}

		// 记录账户变更的日志数据
		this.log(accountLockResultVo.getUserId() , "扣减: " + accountLockResultVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_MINUS , accountLockResultVo.getAmount() , orderNo);

		// 删除Redis中的锁定记录
		redisTemplate.delete(accountLockResultKey) ;
	}

	@Override
	public void unLock(String orderNo) {

		// 考虑接口的幂等性处理   setnx
		String idempotentKey = "account:unlock:idempotent:" + orderNo ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 1, TimeUnit.HOURS);
		if(!ifAbsent) return ;

		// 根据订单的编号，从Redis中查询锁定结果数据
		String accountLockResultKey = "account:lock:result:" + orderNo ;
		String accountLockResultVoJson = (String) redisTemplate.opsForValue().get(accountLockResultKey);
		AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultVoJson, AccountLockResultVo.class);

		// 更改user_account数据库表中的相关字段的值
		int count = userAccountMapper.unLock(accountLockResultVo.getUserId() , accountLockResultVo.getAmount()) ;
		if(count == 0) {
			throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR) ;
		}

		// 记录账户变更的日志数据
		this.log(accountLockResultVo.getUserId() , "解锁: " + accountLockResultVo.getContent() ,
				SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK , accountLockResultVo.getAmount() , orderNo);

		// 删除Redis中的锁定记录
		redisTemplate.delete(accountLockResultKey) ;
	}

	@Override
	@Transactional
	public void updateRechangreStatus(String orderNo) {
// 根据orderNo查询充值记录
		LambdaQueryWrapper<RechargeInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(RechargeInfo::getOrderNo , orderNo) ;
		RechargeInfo rechargeInfo = rechargeInfoMapper.selectOne(lambdaQueryWrapper);
		rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
		rechargeInfoMapper.updateById(rechargeInfo) ;

		// 更改账户的金额
		Long userId = rechargeInfo.getUserId();
		LambdaQueryWrapper<UserAccount> userAccountLambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		userAccountLambdaQueryWrapper.eq(UserAccount::getUserId , userId) ;
		UserAccount userAccount = userAccountMapper.selectOne(userAccountLambdaQueryWrapper);
		userAccount.setTotalAmount(userAccount.getTotalAmount().add(rechargeInfo.getRechargeAmount()));
		userAccount.setAvailableAmount(userAccount.getAvailableAmount().add(rechargeInfo.getRechargeAmount()));
		userAccountMapper.updateById(userAccount) ;

		// 记录账户变更的日志数据
		this.log(userId , "充值" , SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT , rechargeInfo.getRechargeAmount() , orderNo);

	}

	private void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
		UserAccountDetail accountDetail = new UserAccountDetail() ;
		accountDetail.setUserId(userId);
		accountDetail.setTitle(title);
		accountDetail.setTradeType(tradeType);
		accountDetail.setAmount(amount);
		accountDetail.setOrderNo(orderNo);
		userAccountDetailMapper.insert(accountDetail) ;
	}
}
